package com.tacitknowledge.filters.clustercheck;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;

class ClusterCheckingHttpSessionWrapper implements InvocationHandler {
	private HttpSession wrappedSession = null;
	private Map<String, byte[]> sessionData = new HashMap<String, byte[]>();
	private Map<String, List<String>> sessionAccessors = new HashMap<String, List<String>>();

	public ClusterCheckingHttpSessionWrapper(HttpSession session) {
		this.wrappedSession = session;
		this.mirrorSession();
	}

	public HttpSession getWrappedSession() {
		return (HttpSession) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
				new Class[] { javax.servlet.http.HttpSession.class }, this);
	}

	public List<String> validate() {
		LinkedList<String> errors = new LinkedList<String>();
		String currentAttributeName = null;
		Enumeration<String> e = null;

		try {
			e = this.wrappedSession.getAttributeNames();
		} catch (IllegalStateException arg11) {
			return errors;
		}

		while (e.hasMoreElements()) {
			currentAttributeName = (String) e.nextElement();

			try {
				ByteArrayOutputStream ex = new ByteArrayOutputStream();
				ObjectOutputStream os2 = new ObjectOutputStream(ex);
				os2.writeObject(this.wrappedSession.getAttribute(currentAttributeName));
				os2.flush();
				byte[] actualBytes = ex.toByteArray();
				byte[] controlBytes = (byte[]) this.sessionData.get(currentAttributeName);
				if (!Arrays.equals(controlBytes, actualBytes)) {
					StringBuffer buffer = new StringBuffer();
					buffer.append(currentAttributeName + " was modified but setAttribute() was not called.");
					ArrayList<String> accessors = (ArrayList<String>) this.sessionAccessors.get(currentAttributeName);

					for (int i = 0; accessors != null && i < accessors.size(); ++i) {
						String accessor = (String) accessors.get(i);
						buffer.append("\n This was a get after put, it may be the culprit:\n");
						buffer.append(accessor);
					}

					errors.add(new String(buffer));
				}
			} catch (NotSerializableException arg12) {
				errors.add(currentAttributeName + " is not Serializable: " + arg12.getMessage());
			} catch (IOException arg13) {
				errors.add("An exception occurred while checking " + currentAttributeName + ": " + arg13.getMessage());
			}
		}

		return errors;
	}

	public Map<String, Integer> getAttributeSizes() throws IOException {
		HashMap<String, Integer> sizes = new HashMap<String, Integer>();
		String currentAttributeName = null;
		Enumeration<String> e = null;

		try {
			e = this.wrappedSession.getAttributeNames();
		} catch (IllegalStateException arg5) {
			return sizes;
		}

		while (e.hasMoreElements()) {
			currentAttributeName = (String) e.nextElement();
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream os = new ObjectOutputStream(bos);
			os.writeObject(this.wrappedSession.getAttribute(currentAttributeName));
			os.flush();
			sizes.put(currentAttributeName, new Integer(bos.size()));
		}

		return sizes;
	}

	public int getAggregateSize() throws IOException {
		int aggregateSize = 0;
		Map<String, Integer> sizes = this.getAttributeSizes();

		for (Iterator<String> keys = sizes.keySet().iterator(); keys
				.hasNext(); aggregateSize += ((Integer) sizes.get(keys.next())).intValue()) {
			;
		}

		return aggregateSize;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (!method.getName().equals("setAttribute") && !method.getName().equals("putValue")) {
			if (!method.getName().equals("getAttribute") && !method.getName().equals("getValue")) {
				if (!method.getName().equals("removeAttribute") && !method.getName().equals("removeValue")) {
					if (method.getName().equals("invalidate")) {
						this.invalidateSessionData();
					}
				} else {
					this.removeSessionData((String) args[0]);
				}
			} else {
				this.getSessionData((String) args[0]);
			}
		} else {
			this.storeSessionData((String) args[0], args[1]);
		}

		try {
			return method.invoke(this.wrappedSession, args);
		} catch (InvocationTargetException arg4) {
			throw arg4.getTargetException();
		}
	}

	private void storeSessionData(String key, Object value) throws IOException {
		ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
		ObjectOutputStream os1 = new ObjectOutputStream(bos1);
		os1.writeObject(value);
		os1.flush();
		byte[] controlBytes = bos1.toByteArray();
		this.sessionData.put(key, controlBytes);
		this.sessionAccessors.remove(key);
	}

	private void getSessionData(String key) {
		List<String> accessors = this.sessionAccessors.get(key);
		if (accessors == null) {
			accessors = new ArrayList<String>();
		}

		Exception e = new Exception();
		StringWriter stack = new StringWriter();
		e.printStackTrace(new PrintWriter(stack));
		accessors.add(new String(stack.getBuffer()));
		this.sessionAccessors.put(key, accessors);
	}

	private void removeSessionData(String key) {
		this.sessionAccessors.remove(key);
		this.sessionData.remove(key);
	}

	private void invalidateSessionData() {
		this.sessionAccessors.clear();
		this.sessionData.clear();
	}

	private void mirrorSession() {
		Enumeration<String> e = null;

		try {
			e = this.wrappedSession.getAttributeNames();
		} catch (IllegalStateException arg5) {
			return;
		}

		while (e.hasMoreElements()) {
			String name = (String) e.nextElement();
			Object value = this.wrappedSession.getAttribute(name);

			try {
				this.storeSessionData(name, value);
			} catch (IOException arg4) {
				arg4.printStackTrace();
			}
		}

	}
}